﻿using System.Linq;
using NSubstitute;
using Sai.Core;
using Sai.Core.Rules;
using Sai.Test.TestDataBuilders;
using Xunit;
using Sai.Test.XUnit;

namespace Sai.Test.Rules
{
    public class CheckTypeMethodsFacts
    {

        [Fact, TouchingFileSystemTest]
        public void Identical_Class_In_Both_Versions_Shows_Zero_Changes()
        {
            const string className = "Class1";
            const string namespaceName = "ClassLibrary1";
            const string method = "public void MyMethod() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(method);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(method);

            string newCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.False(diff.HasChanged);

            Assert.Null(diff.MethodsAdded);
            Assert.Null(diff.MethodsChanged);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);
        }


        [Fact, TouchingFileSystemTest]
        public void Adding_Abstract_Method_Is_Flagged()
        {
            const string className = "Class1";
            const string namespaceName = "ClassLibrary1";
            const string method = "public void MyMethod() { }";
            const string abstractMethod = "public abstract void MyMethod2();";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(method);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .IsAbstract()
                .WithMethod(method)
                .WithMethod(abstractMethod);

            string newCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();


            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsAdded.Count);
            Assert.Null(diff.MethodsChanged);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);

            UnaryTypeChange change = diff.MethodsAdded.First();

            Assert.Equal(TypeChangeReason.MethodAdded, change.Reason);
            Assert.Contains("Abstract method added", change.Message);
        }


        [Fact, TouchingFileSystemTest]
        public void Adding_Protected_Method_Is_Flagged()
        {
            const string className = "Class1";
            const string namespaceName = "ClassLibrary1";
            const string method = "public void MyMethod() { }";
            const string protectedMethod = "protected void MyMethod2() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(method);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .IsAbstract()
                .WithMethod(method)
                .WithMethod(protectedMethod);

            string newCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsAdded.Count);
            Assert.Null(diff.MethodsChanged);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);

            UnaryTypeChange change = diff.MethodsAdded.First();

            Assert.Equal(TypeChangeReason.MethodAdded, change.Reason);
        }

        [Fact, TouchingFileSystemTest]
        public void Adding_Public_Method_Is_Flagged()
        {
            const string className = "Class1";
            const string publicMethod = "public void MyMethod() { }";
            const string publicMethod2 = "public void MyMethod2() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicMethod);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .IsAbstract()
                .WithMethod(publicMethod)
                .WithMethod(publicMethod2);

            string newCode = BoilerPlateCodeBuilder.BuildFor(newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsAdded.Count);
            Assert.Null(diff.MethodsChanged);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);

            UnaryTypeChange change = diff.MethodsAdded.First();

            Assert.Equal(TypeChangeReason.MethodAdded, change.Reason);
        }

        [Fact, TouchingFileSystemTest]
        public void Changing_Method_Public_To_Protected_Is_Flagged()
        {
            const string className = "Class1";
            const string publicMethod = "public void MyMethod() { }";
            const string protectedMethod = "protected void MyMethod() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicMethod);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .IsAbstract()
                .WithMethod(protectedMethod);

            string newCode = BoilerPlateCodeBuilder.BuildFor(newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsChanged.Count);
            Assert.Null(diff.MethodsAdded);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);

            BinaryTypeChange change = diff.MethodsChanged.First();

            Assert.Equal(TypeChangeReason.MethodChanged, change.Reason);
        }

        [Fact, TouchingFileSystemTest]
        public void Changing_Method_Protected_To_Public_Is_Flagged()
        {
            const string className = "Class1";
            const string publicMethod = "public void MyMethod() { }";
            const string protectedMethod = "protected void MyMethod() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(protectedMethod);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .IsAbstract()
                .WithMethod(publicMethod);

            string newCode = BoilerPlateCodeBuilder.BuildFor(newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsChanged.Count);
            Assert.Null(diff.MethodsAdded);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsRemoved);

            BinaryTypeChange change = diff.MethodsChanged.First();

            Assert.Equal(TypeChangeReason.MethodChanged, change.Reason);
        }

        [Fact, TouchingFileSystemTest]
        public void Removing_Method_Is_Flagged()
        {
            const string className = "Class1";
            const string namespaceName = "ClassLibrary1";
            const string publicMethod1 = "public void MyMethod1() { }";
            const string publicMethod2 = "public void MyMethod2() { }";
            const string publicMethod3 = "public void MyMethod3() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicMethod1)
                .WithMethod(publicMethod2)
                .WithMethod(publicMethod3);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicMethod1)
                .WithMethod(publicMethod3);

            string newCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsRemoved.Count);
            Assert.Null(diff.MethodsAdded);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsChanged);

            UnaryTypeChange change = diff.MethodsRemoved.First();

            Assert.Equal(TypeChangeReason.MethodRemoved, change.Reason);
        }


        [Fact, TouchingFileSystemTest]
        public void Adding_Static_Method_Is_Flagged()
        {
            const string className = "Class1";
            const string namespaceName = "ClassLibrary1";
            const string publicInstanceMethod = "public void MyMethod1() { }";
            const string publicStaticMethod = "public static void MyMethod2() { }";

            var oldClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicInstanceMethod);

            string oldCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, oldClassBuilder);

            var newClassBuilder = CompilableClassBuilder.PublicClass()
                .Named(className)
                .WithMethod(publicInstanceMethod)
                .WithMethod(publicStaticMethod);

            string newCode = BoilerPlateCodeBuilder.BuildFor(namespaceName, newClassBuilder);

            IProductReleaseProject project = Mock.MinimalProject();

            TypeDifference diff = TypeDifferenceBuilder.Differences()
                .CodeWas(oldCode)
                .CodeIsNow(newCode)
                .CheckedUsing(new CheckTypeMethods())
                .Build();

            Assert.True(diff.HasChanged);

            Assert.Equal(1, diff.MethodsAdded.Count);
            Assert.Null(diff.MethodsRemoved);
            Assert.Null(diff.MethodsObsolete);
            Assert.Null(diff.MethodsChanged);

            UnaryTypeChange change = diff.MethodsAdded.First();

            Assert.Equal(TypeChangeReason.MethodAdded, change.Reason);
        }

        // Changing method to/from
        // static 
    }
}
